<?php
/**
 * Nweb Image Manager
 *
 * This script is protected by copyright. It's use, copying, modification
 * and distribution without written consent of the author is prohibited.
 *
 * @category    source
 * @package     pl.nweb.image.adapter
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt  GNU Lesser General Public
 * @version     3.0 $Id: gd.php 105 2012-02-24 09:01:04Z krzysztof.kardasz $
 * @link        http://code.google.com/p/nweb-framework/
 * @link        http://framework.nweb.pl
 */

namespace pl\nweb\image\adapter;

/**
 * Adapter GD
 *
 * @category    source
 * @package     pl.nweb.image.adapter
 * @author      Krzysztof Kardasz <krzysztof.kardasz@gmail.com>
 * @copyright   Copyright (c) 2011 Krzysztof Kardasz
 * @version     3.0 $Id: gd.php 105 2012-02-24 09:01:04Z krzysztof.kardasz $
 */
abstract class Gd implements iAdapter
{
    /**
     * Zasób obrazu
     *
     * @var resource|null
     */
    protected $_im = null;

    /**
     * Szerokość obrazu
     *
     * @var integer|null
     */
    protected $_x = null;

    /**
     * Wysokość obrazu
     *
     * @var integer|null
     */
    protected $_y = null;

    /**
     * Typ obrazu
     *
     * @var integer|null
     */
    protected $_type = null;

    /**
     * Ścieżka do pliku
     *
     * @var string|null
     */
    protected $_file = null;

    /**
     * Typy obsługiwanych typów
     */
    const TYPE_JPEG = IMAGETYPE_JPEG;
    const TYPE_PNG  = IMAGETYPE_PNG;
    const TYPE_GIF  = IMAGETYPE_GIF;

    /**
     * Kody wyjątków
     */
    const Exception_FILE_NOT_FOUND   = 10;
    const Exception_NOT_AN_IMAGE     = 11;
    const Exception_UNSUPPORTED      = 12;
    const Exception_DAMAGED_DATA     = 13;
    const Exception_CROP             = 14;
    const Exception_RESIZE           = 15;
    const Exception_HEADERS_SENT     = 16;
    const Exception_MISSING_FILENAME = 17;
    const Exception_CLONE            = 18;
    const Exception_OUT_OF_RANGE_X   = 19;
    const Exception_OUT_OF_RANGE_Y   = 20;
    const Exception_OUT_OF_RANGE     = 21;

    /**
     * Klonowanie obiektu
     * Zasób obrazu jest tylko referencją, dlatego obraz należy skopiować
     *
     * @return void
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function __clone()
    {
        $new_im = imagecreatetruecolor($this->_x, $this->_y);

        imagealphablending($new_im, false);
        imagesavealpha($new_im, true);
        imagefilledrectangle($new_im, 0, 0, $this->_x, $this->_y, imagecolorallocatealpha($new_im, 255, 255, 255, 127));

        if(!imagecopyresampled($new_im, $this->_im, 0, 0, 0, 0, $this->_x, $this->_y, $this->_x, $this->_y)) {
            throw new gd\Exception('Error during object clone', self::EXCEPTION_CLONE);
        }
        $this->_im = $new_im;
    }

    /**
     * Kopia obiektu
     *
     * @return \pl\nweb\image\adapter\gd
     */
    public function getCopy()
    {
        return clone $this;
    }

    /**
     * Szerokość obrazu
     *
     * @return integer
     */
    public function getX()
    {
        return $this->_x;
    }

    /**
     * Wysokość obrazu
     *
     * @return integer
     */
    public function getY()
    {
        return $this->_y;
    }

    /**
     * Rozszerzenie pliku obrazu na podstawie typu
     *
     * @param  boolean $dot dołączenie kropki
     * @return string
     */
    public function getExtension($dot = false)
    {
        return image_type_to_extension($this->_type, $dot);
    }

    /**
     * Zwraca typ mime obrazu
     *
     * @return string
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function getMime()
    {
        return image_type_to_mime_type($this->_type);
    }

    /**
     * Wycięcie zdjęcia
     *
     * @param  integer $left przesunięcie od lewej w px
     * @param  integer $top przesunięcie od góry w px
     * @param  integer $width szerokość wycięcia w px
     * @param  integer $height wysokość wycięcia w px
     * @return \pl\nweb\image\adapter\gd
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function crop($left, $top, $width, $height)
    {
        $image_x = $this->_x;
        $image_y = $this->_y;

        if (($left == 0 && $width == $image_x) && ($top == 0 && $height == $image_y)) {
            return $this;
        }

        if ($image_x < ($width + $left)) {
            throw new gd\Exception('Image width is out of range', self::EXCEPTION_OUT_OF_RANGE_X);
        } else if ($image_y < ($height + $top)) {
            throw new gd\Exception('Image height is out of range', self::EXCEPTION_OUT_OF_RANGE_Y);
        }

        $new_im = imagecreatetruecolor($width, $height);

        imagealphablending($new_im, false);
        imagesavealpha($new_im, true);
        imagefilledrectangle($new_im, 0, 0, $width, $height, imagecolorallocatealpha($new_im, 255, 255, 255, 127));

        if (!imagecopyresampled($new_im, $this->_im, 0, 0, $left, $top, $width, $height, $width, $height)) {
            throw new gd\Exception('Crop failed', self::EXCEPTION_CROP);
        }

        imagedestroy($this->_im);

        $this->_im = $new_im;
        $this->_x  = imagesx($new_im);
        $this->_y  = imagesy($new_im);

        return $this;
    }

    /**
     * Zmiana rozmiaru zdjęcia
     *
     * @param  integer $width  szerokość w px
     * @param  integer $height wysokość w px
     * @return \pl\nweb\image\adapter\gd
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function resize($width, $height)
    {
        $image_x = $this->_x;
        $image_y = $this->_y;

        if ($width == $image_x &&  $height == $image_y) {
            return $this;
        } else if ($width > $image_x) {
            throw new gd\Exception('Destination width is greater than original width', self::EXCEPTION_OUT_OF_RANGE_X);
        } else if ($height > $image_y) {
            throw new gd\Exception('Destination height is greater than original height', self::EXCEPTION_OUT_OF_RANGE_Y);
        }

        $new_im = imagecreatetruecolor($width, $height);

        imagealphablending($new_im, false);
        imagesavealpha($new_im, true);
        imagefilledrectangle($new_im, 0, 0, $width, $height, imagecolorallocatealpha($new_im, 255, 255, 255, 127));

        if (!imagecopyresampled($new_im, $this->_im, 0, 0, 0, 0, $width, $height, $image_x, $image_y)) {
            throw new gd\Exception('Resize failed', self::EXCEPTION_RESIZE);
        }

        imagedestroy($this->_im);

        $this->_im = $new_im;
        $this->_x  = imagesx($new_im);
        $this->_y  = imagesy($new_im);

        return $this;
    }

    /**
     * Zmiana rozmiaru (uwzględniając proporcje)
     *
     * @param  integer $width  szerokość w px
     * @param  integer $height wysokość w px
     * @return \pl\nweb\image\adapter\gd
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function scale($width, $height)
    {
        if ($width < $this->_x) {
           return $this->resizeX($width);
        } else if($height < $this->_y) {
            return $this->resizeY($height);
        } else {
            throw new gd\Exception('Destination size is greater than original size', self::EXCEPTION_OUT_OF_RANGE);
        }

        return $this;
    }

    /**
     * Zmiana szerokości obrazu (uwzględniając proporcje - zmienia się również wysokość)
     *
     * @param  integer $width  szerokość w px
     * @return Image
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function scaleX($width)
    {
        $height = ($width * $this->_y) / $this->_x;
        return $this->resize($width, $height);
    }

    /**
     * Zmiana wysokości obrazu (uwzględniając proporcje - zmienia się również szerokość)
     *
     * @param  integer $height wysokość w px
     * @return Image
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function scaleY ($height)
    {
        $width = ($this->_x * $height) / $this->_y;
        return $this->resize($width, $height);
    }

    /**
     * Dopasowanie zdjęcie do zadanego rozmiaru
     * skaluje, centruje i obcina zdjęcie do rozmiaru
     *
     * @param  integer $width  szerokość w px
     * @param  integer $height wysokość w px
     * @return \pl\nweb\image\adapter\gd
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function fitTo($width, $height)
    {
        $x = $this->_x;
        $y = $this->_y;

        if ($width == $x && $height == $y) {
            return $this;
        } else if ($width > $x) {
            throw new gd\Exception('Destination width is greater than original width', self::EXCEPTION_OUT_OF_RANGE_X);
        } else if ($height > $y) {
            throw new gd\Exception('Destination height is greater than original height', self::EXCEPTION_OUT_OF_RANGE_Y);
        }

        $x1 = 0;
        $y1 = 0;

        /* Nowa Szerokość */
        $xm1 = round(($y * $width) / $height);

        /* Nowa Wysokość */
        $ym1 = round(($height * $x) / $width);

        /* Jeśli nowa szerokość jest mniejsza od starej */
        if($xm1 < $x) {
            /* Przesunięcie od lewej (wycentrowanie) */
            $x1 = floor(($x-$xm1)/2);
            /* Nowa szerokość */
            $x  = $xm1;
        /* Jeśli nowa wysokość jest mniejsza od starej */
        } elseif ($ym1 < $y) {
            /* Przesunięcie z góry (centrowanie w pionie) */
            $y1 = floor(($y - $ym1)/2);
            /* Nowa wysokość */
            $y  = $ym1;
        }

        $this->crop($x1, $y1, $x, $y);
        $this->resize($width, $height);

        return $this;
    }

    /**
     * Wyświetlnie obrazu
     *
     * @return void
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function display()
    {
        if (headers_sent()) {
            throw new gd\Exception('Cannot display image, headers already sent', self::EXCEPTION_HEADERS_SENT);
        }

        header('Content-Type: ' . $this->getMime());

        switch ($this->_type) {
            case IMAGETYPE_JPEG:
                imagejpeg($this->_im, null, 100);
            break;
            case IMAGETYPE_PNG:
                imagepng($this->_im, null, 0);
            break;
            case IMAGETYPE_GIF:
                imagegif($this->_im);
            break;
        }
    }

    /**
     * Zapisanie obrazu do pliku
     *
     * @param null|string $filename plik docelowy
     * @return void
     * @throws \pl\nweb\image\adapter\gd\Exception
     */
    public function save($filename = null)
    {
        if (null === $filename) {
            $filename = $this->_file;
        }

        if (null === $filename) {
            throw new gd\Exception('Filename is required', self::EXCEPTION_MISSING_FILENAME);
        }
        switch ($this->_type) {
            case IMAGETYPE_JPEG:
                imagejpeg($this->_im, $filename, 100);
            break;
            case IMAGETYPE_PNG:
                imagepng($this->_im, $filename, 0);
            break;
            case IMAGETYPE_GIF:
                imagegif($this->_im, $filename);
            break;
        }
    }

    /**
     * Zwalnia pamięć
     *
     * @return void
     */
    public function __destruct()
    {
        if (null !== $this->_im) {
            imagedestroy($this->_im);
        }
    }
}